package fr.upmc.aladyn.transactionables.javassist.injection;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.Translator;
import javassist.expr.Handler;
import javassist.expr.ExprEditor;

/**
 * <p>Javassist translator used to inject code to <code>Transactionnable</code> methods and Setters.</p>
 * @author Alexis Deluze
 */
public class TransactionnableTranslator implements Translator {
	
	private static final String _INIT = "{ fr.upmc.aladyn.transactionables.saveobject.StackManager.getStackManager().newStackBlock(); }";
	private static final String _UNINIT = "{ fr.upmc.aladyn.transactionables.saveobject.StackManager.getStackManager().pop(); }";
	private static final String _SAVE = "{ fr.upmc.aladyn.transactionables.saveobject.StackManager.getStackManager().save(this); }";
	private static final String _RESTORE = "{ fr.upmc.aladyn.transactionables.saveobject.StackManager.getStackManager().restore(); }";
	private static final String _RESTORE_THROW = "{ fr.upmc.aladyn.transactionables.saveobject.StackManager.getStackManager().restore(); throw $e;}";	

	/**
	 * <p>Inject the save and restoration code for every @Transactionnable method found. Inject the save code for every Setter found.</p>
	 * @param pool The class pool
	 * @param classname The name of the loaded class
	 * @throws NotFoundException
	 * @throws CannotCompileException
	 * @see javassist.Translator#onLoad(javassist.ClassPool, java.lang.String)
	 */
	@Override
	public void onLoad(ClassPool pool, String classname) throws NotFoundException, CannotCompileException {
		// recuperation de la classe a modifier
		CtClass cc = pool.get(classname);
		System.out.println("[JAVASSIST] Chargement de la classe "+cc.getSimpleName());
		
		// parcours des methodes
		for(CtMethod cm : cc.getDeclaredMethods()){

			if(cm.getName().startsWith("set")){
				System.out.println("\t- La methode "+cm.getName()+" est un setter, on injecte le code de sauvegarde");
				cm.insertBefore(_SAVE);
			}else{
					
				// si la methode est transactionnable
				if(cm.hasAnnotation(fr.upmc.aladyn.transactionables.annotations.Transactionable.class)){
					
					System.out.println("\t- La methode "+cm.getName()+" est Transactionnable");
					
					// ajout du code de restauration dans chaque clause Catch existante
					System.out.println("\t\t- Injection du code de restauration dans les Catch existants");
					cm.instrument(
						new ExprEditor() {
							public void edit(Handler h) throws CannotCompileException {
								System.out.println("\t\t\t- Ligne numero : "+h.getLineNumber());
								h.insertBefore(_RESTORE);
							}
						}
					);
					
					// on ajoute un try catch englobant pour la restauration
					System.out.println("\t\t- Ajout d'un Catch et injection du code de restauration + Throw de l'eventuelle exception");
					cm.addCatch(_RESTORE_THROW, pool.get("java.lang.Throwable"));
					
					// on insert notre code d'initialisation de la sauvegarde
					System.out.println("\t\t- Injection du code d'initialisation de la sauvegarde au debut de la methode");
					cm.insertBefore(_INIT);
					
					// supression du bloc de sauvegarde dans la pile
					System.out.println("\t\t- Injection du code de supression de la sauvegarde ala fin de la methode");
					cm.insertAfter(_UNINIT);
				}
			}
		}
	}
	
	
	/**
	 * <p>Method called at the initialization of the TransactionnableTranslator. Does nothing.</p>
	 * @param pool the class pool
	 * @throws NotFoundException
	 * @throws CannotCompileException
	 * @see javassist.Translator#start(javassist.ClassPool)
	 */
	@Override
	public void start(ClassPool pool) throws NotFoundException, CannotCompileException {}
}